package me.flyray.bsin.infrastructure.biz;

import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import me.flyray.bsin.blockchain.connection.BsinBlockChainEngineFactory;
import me.flyray.bsin.blockchain.core.BsinBlockChainEngine;
import me.flyray.bsin.domain.constants.WalletConstants;
import me.flyray.bsin.domain.entity.*;
import me.flyray.bsin.exception.BusinessException;
import me.flyray.bsin.infrastructure.mapper.ChainCoinMapper;
import me.flyray.bsin.infrastructure.mapper.ChainSettlementAccountMapper;
import me.flyray.bsin.infrastructure.mapper.WalletAccountMapper;
import me.flyray.bsin.infrastructure.mapper.WalletMapper;
import me.flyray.bsin.infrastructure.utils.OkHttpUtils;
import me.flyray.bsin.mq.producer.RocketMQProducer;
import me.flyray.bsin.utils.BsinSnowflake;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;

@Service
public class WalletAccountBiz {

    private static final Logger log = LoggerFactory.getLogger(WalletAccountBiz.class);
    @Autowired
    private WalletAccountMapper walletAccountMapper;
    @Autowired
    private ChainCoinMapper chainCoinMapper;
    @Autowired
    private WalletMapper walletMapper;
    @Autowired
    private RocketMQProducer rocketMQProducer;
    @Value("${rocketmq.consumer.topic}")
    private String topic;
    @Value("${bsin.blockchain.gateway-url:http://127.0.0.1:8125}")
    private String appChainGatewayUrl;
    @Autowired private BsinBlockChainEngineFactory bsinBlockChainEngineFactory;
    @Value("${bsin.blockchain.wallet.password:}")
    private String defaultWalletPassword;
    @Value("${bsin.blockchain.wallet.chain-env:mainnet}")
    private String defaultChainEnv;
    @Autowired
    private ChainSettlementAccountMapper chainSettlementAccountMapper;

    /**
     * 构建MPC配置请求（已废弃，仅用于兼容旧代码）
     */
    private JSONObject buildMpcConfigRequest(Wallet wallet) {
        JSONObject config = new JSONObject();
        config.put("walletName", wallet != null ? wallet.getWalletName() : "");
        config.put("walletType", WalletConstants.MPC_WALLET_TYPE);
        config.put("chainType", WalletConstants.MPC_CHAIN_TYPE);
        config.put("threshold", WalletConstants.MPC_DEFAULT_THRESHOLD);
        config.put("partyNum", WalletConstants.MPC_DEFAULT_PARTY_NUM);
        config.put("mpcClients", Arrays.asList(0, 1));
        config.put("sync", WalletConstants.MPC_DEFAULT_SYNC);
        config.put("timeout", WalletConstants.MPC_DEFAULT_TIMEOUT);
        return config;
    }

    /**
     * 创建钱包账户
     *  1、创建链上钱包并返回地址
     *  2、创建钱包地址
     *  3、创建钱包账户
     * @param wallet
     * @param chainCoinNo
     */
    @Transactional(rollbackFor = Exception.class)
    public WalletAccount createWalletAccount(Wallet wallet, ChainCoin chainCoin) throws Exception {
        log.info("开始创建钱包账户，wallet:{},chainCoinNo:{}",wallet, chainCoin);
        
        // 1、验证币种
        if(chainCoin == null || WalletConstants.COIN_STATUS_DISABLED.equals(chainCoin.getStatus())){
            throw new BusinessException("CHAIN_COIN_NOT_EXISTS_OR_OFF_SHELVES");
        }
        
        // 2、调用区块链引擎创建钱包
        BsinBlockChainEngine bsinBlockChainEngine =
                bsinBlockChainEngineFactory.getBsinBlockChainEngineInstance(chainCoin.getChainIdentifier());
        
        // 使用配置的密码和环境
        String password = StringUtils.isNotBlank(defaultWalletPassword) ? defaultWalletPassword : "";
        String chainEnv = StringUtils.isNotBlank(wallet.getEnv()) ? wallet.getEnv() : defaultChainEnv;
        
        Map<String, Object> walletAccountInfo = bsinBlockChainEngine.createWallet(password, chainEnv);
        
        // 3、验证返回的地址信息
        String address = (String) walletAccountInfo.get("address");
        if (StringUtils.isBlank(address)) {
            throw new BusinessException("BLOCKCHAIN_ADDRESS_GENERATION_FAILED");
        }
        
        // 4、创建钱包账户
        String walletAccountNo = BsinSnowflake.getId();
        WalletAccount walletAccount = new WalletAccount();
        walletAccount.setSerialNo(walletAccountNo);
        walletAccount.setChainCoinNo(chainCoin.getSerialNo());
        walletAccount.setChainIdentifier(chainCoin.getChainIdentifier());
        walletAccount.setStatus(WalletConstants.ACCOUNT_STATUS_NORMAL);
        walletAccount.setWalletNo(wallet.getSerialNo());
        walletAccount.setAddress(address);
        walletAccount.setPublicKey((String) walletAccountInfo.get("publicKey"));
        walletAccount.setPrivateKey((String) walletAccountInfo.get("privateKey"));
        walletAccount.setBalance(BigDecimal.ZERO);
        
        // 计算地址校验码
        walletAccount.setCheckCode(MD5.create().digestHex(address));
        
        walletAccount.setTenantId(wallet.getTenantId());
        walletAccount.setDelFlag(WalletConstants.DEL_FLAG_NORMAL);
        walletAccount.setCreateTime(new Date());
        walletAccountMapper.insert(walletAccount);
        
        log.info("结束创建钱包账户，wallet:{}, chainCoinNo:{}, address:{}", wallet, chainCoin.getSerialNo(), address);
        return walletAccount;
    }

    /**
     * 创建钱包账户
     *  1、创建链上钱包并返回地址
     *  2、创建钱包地址
     *  3、创建钱包账户
     * @param wallet
     * @param chainCoinNo
     */
    @Transactional(rollbackFor = Exception.class)
    public WalletAccount createMpcWalletAccount(Wallet wallet, String chainCoinNo) {
        log.info("开始创建MPC钱包账户，wallet:{},chainCoinNo:{}",wallet,chainCoinNo);
        
        // 1、验证币种
        ChainCoin chainCoin = chainCoinMapper.selectById(chainCoinNo);
        if(chainCoin == null || WalletConstants.COIN_STATUS_DISABLED.equals(chainCoin.getStatus())){
            throw new BusinessException("CHAIN_COIN_NOT_EXISTS_OR_OFF_SHELVES");
        }
        
        // 2、调用MPC网络生成密钥
        JSONObject mpcConfig = buildMpcConfigRequest(wallet);
        JSONObject data = OkHttpUtils.httpPost(appChainGatewayUrl + "/api/v1/mpc/keygen", mpcConfig);
        String requisitionId = (String) data.get("requisitionId");
        log.info("MPC网络返回请求ID：{}", requisitionId);
        
        // 3、创建钱包账户（地址待MPC网络异步返回）
        String walletAccountNo = BsinSnowflake.getId();
        WalletAccount walletAccount = new WalletAccount();
        walletAccount.setSerialNo(walletAccountNo);
        walletAccount.setChainCoinNo(chainCoinNo);
        walletAccount.setStatus(WalletConstants.ACCOUNT_STATUS_NORMAL);
        walletAccount.setWalletNo(wallet.getSerialNo());
        walletAccount.setBalance(BigDecimal.ZERO);
        walletAccount.setTenantId(wallet.getTenantId());
        walletAccount.setDelFlag(WalletConstants.DEL_FLAG_NORMAL);
        walletAccount.setCreateTime(new Date());
        walletAccountMapper.insert(walletAccount);
        log.info("结束创建MPC钱包账户，wallet:{}, chainCoinNo:{}", wallet, chainCoinNo);

        // 4、发送延时消息到MQ，等待MPC网络生成地址
        JSONObject mQMsgReq = new JSONObject();
        mQMsgReq.put("requisitionId", requisitionId);
        mQMsgReq.put("eventCode", WalletConstants.EVENT_CODE_CREATE_MPC_WALLET);
        mQMsgReq.put("walletAccountNo", walletAccountNo);
        
        SendCallback callback = new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("MPC钱包消息发送成功，messageId: {}, walletAccountNo: {}", 
                    sendResult.getMsgId(), walletAccountNo);
            }
            @Override
            public void onException(Throwable throwable) {
                log.error("MPC钱包消息发送失败，walletAccountNo: {}, 错误: {}", 
                    walletAccountNo, throwable.getMessage(), throwable);
            }
        };
        
        // 延时等级7 = 3分钟，等待MPC网络生成密钥
        rocketMQProducer.sendDelay(topic, mQMsgReq.toString(), callback, WalletConstants.MQ_DELAY_LEVEL_3_MINUTES);
        return walletAccount;
    }


    /**
     * 添加钱包账户
     *  1、导入钱包公私钥
     * @param chainCoinNo
     */
    public WalletAccount addWalletAccount(String walletNo,String chainCoinNo, String privateKey, String pubKey, String address) {
        log.info("开始添加钱包账户，walletNo:{}, chainCoinNo:{}",walletNo, chainCoinNo);
        
        // 0、参数校验
        if (StringUtils.isBlank(walletNo)) {
            throw new BusinessException("WALLET_NO_CANNOT_BE_EMPTY");
        }
        if (StringUtils.isBlank(chainCoinNo)) {
            throw new BusinessException("CHAIN_COIN_NO_CANNOT_BE_EMPTY");
        }
        if (StringUtils.isBlank(address)) {
            throw new BusinessException("ADDRESS_CANNOT_BE_EMPTY");
        }
        
        // 1、验证钱包是否存在
        Wallet wallet = walletMapper.selectById(walletNo);
        if(wallet == null || !WalletConstants.DEL_FLAG_NORMAL.equals(wallet.getDelFlag())){
            throw new BusinessException("WALLET_NOT_EXISTS");
        }
        
        // 2、验证币种是否存在
        ChainCoin chainCoin = chainCoinMapper.selectById(chainCoinNo);
        if(chainCoin == null || WalletConstants.COIN_STATUS_DISABLED.equals(chainCoin.getStatus())){
            throw new BusinessException("CHAIN_COIN_NOT_EXISTS_OR_OFF_SHELVES");
        }
        
        // 2.5、检查账户是否已存在（防止重复添加）
        LambdaQueryWrapper<WalletAccount> checkWrapper = new LambdaQueryWrapper<>();
        checkWrapper.eq(WalletAccount::getWalletNo, walletNo)
                   .eq(WalletAccount::getChainCoinNo, chainCoinNo)
                   .eq(WalletAccount::getDelFlag, WalletConstants.DEL_FLAG_NORMAL);
        long count = walletAccountMapper.selectCount(checkWrapper);
        if (count > 0) {
            throw new BusinessException("WALLET_ACCOUNT_ALREADY_EXISTS");
        }
        
        // 3、创建钱包账户
        String walletAccountNo = BsinSnowflake.getId();
        WalletAccount walletAccount = new WalletAccount();
        walletAccount.setSerialNo(walletAccountNo);
        walletAccount.setChainCoinNo(chainCoinNo);
        walletAccount.setStatus(WalletConstants.ACCOUNT_STATUS_NORMAL);
        walletAccount.setWalletNo(walletNo);
        walletAccount.setBalance(BigDecimal.ZERO);
        walletAccount.setPublicKey(pubKey);
        walletAccount.setAddress(address);
        walletAccount.setPrivateKey(privateKey);
        walletAccount.setTenantId(wallet.getTenantId());  // 使用钱包的tenantId，确保查询时能匹配
        walletAccount.setDelFlag(WalletConstants.DEL_FLAG_NORMAL);
        walletAccount.setCreateTime(new Date());
        walletAccountMapper.insert(walletAccount);
        log.info("结束添加钱包账户，walletAccountNo:{}, walletNo:{}, chainCoinNo:{}", walletAccountNo, walletNo, chainCoinNo);
        return walletAccount;
    }

    /**
     * 获取平台归集账户
     * TODO 查询平台配置的钱包处理平台归集钱包
     */
    @Transactional(rollbackFor = Exception.class)
    public ChainSettlementAccount getGatherAccount(String tenantId, String bizRoleTypeNo, String chainCoinNo) throws Exception {

        // 1、查询平台归集钱包
        LambdaQueryWrapper<ChainSettlementAccount> chainSettlementAccountWrapper = new LambdaQueryWrapper<>();
        chainSettlementAccountWrapper.eq(ChainSettlementAccount::getTenantId, tenantId);
        chainSettlementAccountWrapper.eq(ChainSettlementAccount::getBizRoleTypeNo, bizRoleTypeNo);
        chainSettlementAccountWrapper.eq(ChainSettlementAccount::getChainCoinNo, chainCoinNo);
        ChainSettlementAccount chainSettlementAccount = chainSettlementAccountMapper.selectOne(chainSettlementAccountWrapper);
        
        if (chainSettlementAccount == null) {
            throw new BusinessException("PLATFORM_GATHER_WALLET_ACCOUNT_NOT_EXISTS");
        }
        
        return chainSettlementAccount;
    }

    /**
     * 查询MPC网络钱包地址并更新
     * 1、查询MPC网络钱包地址
     * 2、更新用户的钱包地址
     */
    @Transactional(rollbackFor = Exception.class)
    public void getAppChainWalletAddress(JSONObject mQMsg) {
        log.info("开始处理MPC钱包地址查询，消息：{}", mQMsg);
        
        try {
            String requisitionId = (String) mQMsg.get("requisitionId");
            String walletAccountNo = (String) mQMsg.get("walletAccountNo");
            
            if (StringUtils.isBlank(requisitionId) || StringUtils.isBlank(walletAccountNo)) {
                log.error("MPC消息参数不完整，requisitionId: {}, walletAccountNo: {}", requisitionId, walletAccountNo);
                return;
            }
            
            // 1、查询MPC网络生成的地址
            String url = appChainGatewayUrl + "/api/v1/mpc/requisition/" + requisitionId;
            JSONObject result = OkHttpUtils.httpGet(url);
            log.info("MPC网络返回信息：{}", result);
            
            String address = (String) result.get("address");
            String pubKey = (String) result.get("pubKey");
            
            // 2、验证返回的地址
            if (StringUtils.isEmpty(address)) {
                log.warn("MPC网络地址生成中，requisitionId: {}, 稍后重试", requisitionId);
                return;
            }
            
            // 3、更新钱包账户地址
            WalletAccount walletAccount = new WalletAccount();
            walletAccount.setSerialNo(walletAccountNo);
            walletAccount.setAddress(address);
            walletAccount.setPublicKey(pubKey);
            walletAccount.setCheckCode(MD5.create().digestHex(address));
            
            int updated = walletAccountMapper.updateById(walletAccount);
            if (updated > 0) {
                log.info("MPC钱包地址更新成功，walletAccountNo: {}, address: {}", walletAccountNo, address);
            } else {
                log.error("MPC钱包地址更新失败，walletAccountNo: {}", walletAccountNo);
            }
            
        } catch (Exception e) {
            log.error("处理MPC钱包地址失败，消息: {}, 错误: {}", mQMsg, e.getMessage(), e);
            throw new BusinessException("MPC_WALLET_ADDRESS_UPDATE_FAILED", e);
        }
    }

}
